Command alias or Alias command #1091
authorStephen Becker IV <github@deathbyescalator.com>
Thu, 12 May 2016 16:30:57 +0000 (09:30 -0700)
committerStephen Becker IV <github@deathbyescalator.com>
Thu, 7 Jul 2016 04:31:46 +0000 (21:31 -0700)
Dearest Reviewer,

This pull request closes #1091 which is a request to support aliases.
This is not as powerful as something like git's alias, however, I think
it sticks true to the original request.

I high jack the processing of the args. After a few flags are checked
and the args are parsed I check the config file for alias.COMMAND. If it
is there I split it like args does and replace args[1] (the original
command) with the alias command and its 'flags'.

I have also included default short hand commands b, t and r.

Thanks!
Becker

src/bin/cargo.rs
src/doc/config.md
tests/cargo_alias_config.rs [new file with mode: 0644]

index 0cb9cb9f185f99e42f0546f2f868d644131e4639..1f065851d3edd8a77c9306575b8819a70b26954f 100644 (file)
@@ -7,13 +7,14 @@ extern crate toml;
 #[macro_use] extern crate log;
 
 use std::collections::BTreeSet;
+use std::collections::HashMap;
 use std::env;
 use std::fs;
 use std::path::{Path,PathBuf};
 
 use cargo::core::shell::Verbosity;
 use cargo::execute_main_without_stdin;
-use cargo::util::{self, CliResult, lev_distance, Config, human};
+use cargo::util::{self, CliResult, lev_distance, Config, human, CargoResult};
 use cargo::util::CliError;
 use cargo::util::process_builder::process;
 
@@ -138,7 +139,7 @@ fn execute(flags: Flags, config: &Config) -> CliResult<Option<()>> {
         return Ok(None)
     }
 
-    let args = match &flags.arg_command[..] {
+    let mut args = match &flags.arg_command[..] {
         // For the commands `cargo` and `cargo help`, re-execute ourselves as
         // `cargo -h` so we can go through the normal process of printing the
         // help message.
@@ -166,9 +167,30 @@ fn execute(flags: Flags, config: &Config) -> CliResult<Option<()>> {
         // For all other invocations, we're of the form `cargo foo args...`. We
         // use the exact environment arguments to preserve tokens like `--` for
         // example.
-        _ => env::args().collect(),
+        _ => {
+            let mut default_alias = HashMap::new();
+            default_alias.insert("b", "build".to_string());
+            default_alias.insert("t", "test".to_string());
+            default_alias.insert("r", "run".to_string());
+            let mut args: Vec<String> = env::args().collect();
+            if let Some(new_command) = default_alias.get(&args[1][..]){
+                args[1] = new_command.clone();
+            }
+            args
+        }
     };
 
+    let alias_list = try!(aliased_command(&config, &args[1]));
+    if let Some(alias_command) = alias_list {
+        // Replace old command with new command and flags
+        let chain = args.iter().take(1)
+            .chain(alias_command.iter())
+            .chain(args.iter().skip(2))
+            .map(|s| s.to_string())
+            .collect();
+        args = chain;
+    }
+
     macro_rules! cmd{
         ($name:ident) => (if args[1] == stringify!($name).replace("_", "-") {
             config.shell().set_verbosity(Verbosity::Verbose);
@@ -186,6 +208,30 @@ fn execute(flags: Flags, config: &Config) -> CliResult<Option<()>> {
     Ok(None)
 }
 
+fn aliased_command(config: &Config, command: &String) -> CargoResult<Option<Vec<String>>> {
+    let alias_name = format!("alias.{}", command);
+    let mut result = Ok(None);
+    match config.get_string(&alias_name) {
+        Ok(value) => {
+            if let Some(record) = value {
+                let alias_commands = record.val.split_whitespace()
+                                               .map(|s| s.to_string())
+                                               .collect();
+                result = Ok(Some(alias_commands));
+            }
+        },
+        Err(_) => {
+            let value = try!(config.get_list(&alias_name));
+            if let Some(record) = value {
+                let alias_commands: Vec<String> = record.val.iter()
+                                .map(|s| s.0.to_string()).collect();
+                result = Ok(Some(alias_commands));
+            }
+        }
+    }
+    result
+}
+
 fn find_closest(config: &Config, cmd: &str) -> Option<String> {
     let cmds = list_commands(config);
     // Only consider candidates with a lev_distance of 3 or less so we don't
index 3b265e40abef29700d4870069a91eb4204fff178..538b31822e093686c79e378878ec010e8fc41c93 100644 (file)
@@ -93,6 +93,15 @@ color = 'auto'         # whether cargo colorizes output
 # Network configuration
 [net]
 retry = 2 # number of times a network call will automatically retried
+
+# Alias cargo commands. The first 3 aliases are built in. If your
+command requires grouped whitespace use the list format.
+[alias]
+b = "build"
+t = "test"
+r = "run"
+rr = "run --release"
+space_example = ["run", "--release", "--", "\"command list\""]
 ```
 
 # Environment Variables
diff --git a/tests/cargo_alias_config.rs b/tests/cargo_alias_config.rs
new file mode 100644 (file)
index 0000000..52453a1
--- /dev/null
@@ -0,0 +1,103 @@
+extern crate cargotest;
+extern crate hamcrest;
+use cargotest::support::{project, execs, basic_bin_manifest};
+use hamcrest::{assert_that};
+
+#[test]
+fn alias_incorrect_config_type() {
+    let p = project("foo")
+        .file("Cargo.toml", &basic_bin_manifest("foo"))
+        .file("src/main.rs", r#"
+            fn main() {
+        }"#)
+        .file(".cargo/config",r#"
+            [alias]
+            b-cargo-test = 5
+        "#);;
+
+    assert_that(p.cargo_process("b-cargo-test").arg("-v"),
+                execs().with_status(101).
+                with_stderr_contains("[ERROR] invalid configuration \
+for key `alias.b-cargo-test`
+expected a list, but found a integer in [..]"));
+}
+
+
+#[test]
+fn alias_default_config_overrides_config() {
+    let p = project("foo")
+        .file("Cargo.toml", &basic_bin_manifest("foo"))
+        .file("src/main.rs", r#"
+            fn main() {
+        }"#)
+        .file(".cargo/config",r#"
+            [alias]
+            b = "not_build"
+        "#);;
+
+    assert_that(p.cargo_process("b").arg("-v"),
+                execs().with_status(0).
+                with_stderr_contains("[COMPILING] foo v0.5.0 [..]"));
+}
+
+#[test]
+fn alias_config() {
+    let p = project("foo")
+        .file("Cargo.toml", &basic_bin_manifest("foo"))
+        .file("src/main.rs", r#"
+            fn main() {
+        }"#)
+        .file(".cargo/config",r#"
+            [alias]
+            b-cargo-test = "build"
+        "#);;
+
+    assert_that(p.cargo_process("b-cargo-test").arg("-v"),
+                execs().with_status(0).
+                with_stderr_contains("[COMPILING] foo v0.5.0 [..]
+[RUNNING] `rustc [..] --crate-name foo --crate-type \
+bin -g --out-dir [..] --emit=dep-info,link -L dependency=[..]\
+-L dependency=[..]"));
+}
+
+#[test]
+fn alias_list_test() {
+    let p = project("foo")
+        .file("Cargo.toml", &basic_bin_manifest("foo"))
+        .file("src/main.rs", r#"
+            fn main() {
+         }"#)
+        .file(".cargo/config",r#"
+            [alias]
+            b-cargo-test = ["build", "--release"]
+         "#);;
+
+    assert_that(p.cargo_process("b-cargo-test").arg("-v"),
+                execs().with_status(0).
+                with_stderr_contains("[COMPILING] foo v0.5.0 [..]").
+                with_stderr_contains("[RUNNING] `rustc [..] --crate-name foo \
+                                     --crate-type bin -C opt-level=3 --out-dir [..]\
+                                     --emit=dep-info,link -L dependency=[..]")
+                );
+}
+
+#[test]
+fn alias_with_flags_config() {
+    let p = project("foo")
+        .file("Cargo.toml", &basic_bin_manifest("foo"))
+        .file("src/main.rs", r#"
+            fn main() {
+         }"#)
+        .file(".cargo/config",r#"
+            [alias]
+            b-cargo-test = "build --release"
+         "#);;
+
+    assert_that(p.cargo_process("b-cargo-test").arg("-v"),
+                execs().with_status(0).
+                with_stderr_contains("[COMPILING] foo v0.5.0 [..]").
+                with_stderr_contains("[RUNNING] `rustc [..] --crate-name foo \
+                                     --crate-type bin -C opt-level=3 --out-dir [..]\
+                                     --emit=dep-info,link -L dependency=[..]")
+                );
+}